#!/bin/bash

VERSION=0.7.6
PROGNAME="$(basename "$0")"

export LC_ALL=C

SCRIPT_UMASK=0122
umask $SCRIPT_UMASK

phead() {
    echo "linux-router $VERSION (https://github.com/garywill/linux-router)"
}
phead2() {
    echo "Released under LGPL, with no warranty. Use on your own risk."
}
usage() {
    phead
    phead2
    cat << EOF

Usage: $PROGNAME <options>

Options:
    -h, --help              Show this help
    --version               Print version number

    -i <interface>          Interface to make NATed sub-network,
                            and to provide Internet to
                            (To create WiFi hotspot use '--ap' instead)
    -o <interface>          Specify an inteface to provide Internet from.
                            (Note using this with default DNS option may leak
                            queries to other interfaces)
    -n                      Do not provide Internet
    --ban-priv              Disallow clients to access my private network
    
    -g <ip>                 This host's IPv4 address in subnet (mask is /24)
                            (example: '192.168.5.1' or '5' shortly)
    -6                      Enable IPv6 (NAT)
    --no4                   Disable IPv4 Internet (not forwarding IPv4).
                            Usually used with '-6'
                            
    --p6 <prefix>           Set IPv6 LAN address prefix (length 64) 
                            (example: 'fd00:0:0:5::' or '5' shortly) 
                            Using this enables '-6'
                            
    --dns <ip>|<port>|<ip:port>
                            DNS server's upstream DNS.
                            Use ',' to seperate multiple servers
                            (default: use /etc/resolv.conf)
                            (Note IPv6 addresses need '[]' around)
    --no-dns                Do not serve DNS
    --no-dnsmasq            Disable dnsmasq server (DHCP, DNS, RA)
    --catch-dns             Transparent DNS proxy, redirect packets(TCP/UDP) 
                            whose destination port is 53 to this host
    --log-dns               Show DNS query log (dnsmasq)
    --dhcp-dns <IP1[,IP2]>|no
                            Set IPv4 DNS offered by DHCP (default: this host).
    --dhcp-dns6 <IP1[,IP2]>|no
                            Set IPv6 DNS offered by DHCP (RA) 
                            (default: this host)
                            (Note IPv6 addresses need '[]' around)
                            Using both above two will enable '--no-dns' 
    --hostname <name>       DNS server associate this name with this host.
                            Use '-' to read name from /etc/hostname
    -d                      DNS server will take into account /etc/hosts
    -e <hosts_file>         DNS server will take into account additional 
                            hosts file
    --dns-nocache           DNS server no cache
    
    --mac <MAC>             Set MAC address
    --random-mac            Use random MAC address
 
    --tp <port>             Transparent proxy,
                            redirect non-LAN TCP and UDP(not tested) traffic to
                            port. (usually used with '--dns')
    
  WiFi hotspot options:
    --ap <wifi interface> <SSID>
                            Create WiFi access point
    -p, --password <password>   
                            WiFi password
    --qr                    Show WiFi QR code in terminal (need qrencode)
    
    --hidden                Hide access point (not broadcast SSID)
    --no-virt               Do not create virtual interface
                            Using this you can't use same wlan interface
                            for both Internet and AP
    --virt-name <name>      Set name of virtual interface
    -c <channel>            Specify channel (default: use current, or 1 / 36)
    --country <code>        Set two-letter country code for regularity
                            (example: US)
    --freq-band <GHz>       Set frequency band: 2.4 or 5 (default: 2.4)
    --driver                Choose your WiFi adapter driver (default: nl80211)
    -w <WPA version>        '2' for WPA2, '1' for WPA, '1+2' for both
                            (default: 2)
    --psk                   Use 64 hex digits pre-shared-key instead of
                            passphrase
    --mac-filter            Enable WiFi hotspot MAC address filtering
    --mac-filter-accept     Location of WiFi hotspot MAC address filter list
                            (defaults to /etc/hostapd/hostapd.accept)
    --hostapd-debug <level> 1 or 2. Passes -d or -dd to hostapd
    --isolate-clients       Disable wifi communication between clients
    --no-haveged            Do not run haveged automatically when needed
    --hs20                  Enable Hotspot 2.0

    WiFi 4 (802.11n) configs:
    --wifi4                 Enable IEEE 802.11n (HT)
    --req-ht                Require station HT (High Throughput) mode
    --ht-capab <HT caps>    HT capabilities (default: [HT40+])

    WiFi 5 (802.11ac) configs:
    --wifi5                 Enable IEEE 802.11ac (VHT)
    --req-vht               Require station VHT (Very High Thoughtput) mode
    --vht-capab <VHT caps>  VHT capabilities
    
    --vht-ch-width <index>  Index of VHT channel width:
                                0 for 20MHz or 40MHz (default)
                                1 for 80MHz
                                2 for 160MHz
                                3 for 80+80MHz (Non-contigous 160MHz)    
    --vht-seg0-ch <channel> Channel index of VHT center frequency for primary 
                            segment. Use with '--vht-ch-width'
    --vht-seg1-ch <channel> Channel index of VHT center frequency for secondary
                            (second 80MHz) segment. Use with '--vht-ch-width 3'

  Instance managing:
    --daemon                Run in background
    -l, --list-running      Show running instances
    --lc, --list-clients <id|interface>     
                            List clients of an instance. Or list neighbors of
                            an interface, even if it isn't handled by us.
                            (passive mode)
    --stop <id>             Stop a running instance
        For <id> you can use PID or subnet interface name.
        You can get them with '--list-running'
                
Examples:
    $PROGNAME -i eth1
    $PROGNAME --ap wlan0 MyAccessPoint -p MyPassPhrase
    $PROGNAME -i eth1 --tp <transparent-proxy> --dns <dns-proxy>
EOF
}

check_empty_option(){
    if [[ "$1" == "" ]]; then
        usage
        exit 0
    fi
}


define_global_variables(){
    # user options
    GATEWAY4=  # IPv4 address for this host
    PREFIX6=  # IPv6 LAN address prefix for this host
    IID6=1    # IPv6 LAN ID for this host
    IPV6=0  # enable ipv6
    NO4=0   # no IPv4 Internet
    BANLAN=0 # ban clients from accessing private addresses
    DHCP_DNS=gateway  # which ipv4 DNS the DHCP gives clients
    DHCP_DNS6=gateway # which ipv6 DNS the DHCP gives clients
    dnsmasq_NO_DNS=0  # disable dns server
    NO_DNSMASQ=0  # disable dnsmasq (dns and dhcp)
    CATCH_DNS=0   # catch clients 53 port packets
    SHOW_DNS_QUERY=0  # log dns
    ETC_HOSTS=0
    ADDN_HOSTS=
    DNS_NOCACHE=
    CONN_IFACE=    # which interface user choose to use to create network
    INTERNET_IFACE= # which interface to get Internet from
    THISHOSTNAME=   # this host's name the DNS tells clients 
    TP_PORT=  # transparent proxy port
    DNS=  # upstream DNS
    MAC_USE_RANDOM=0
    NEW_MACADDR=
    DAEMONIZE=0
    
    # script variables
    SUBNET_IFACE=  # which interface to create network
    SHARE_METHOD=nat 
    OLD_MACADDR=
    SUBNET_NET4=
    SUBNET_NET6=
    

    ##### wifi hotspot
    # user options
    HIDDEN=0 # hidden wifi hotspot
    WIFI_IFACE=
    CHANNEL=default 
    HOTSPOT20=0 # For enabling Hotspot 2.0
    WPA_VERSION=2
    MAC_FILTER=0
    MAC_FILTER_ACCEPT=/etc/hostapd/hostapd.accept
    IEEE80211N=0
    REQUIREHT=0
    IEEE80211AC=0
    REQUIREVHT=0
    HT_CAPAB='[HT40+]'
    VHT_CAPAB=
    VHTCHANNELWIDTH=0
    VHTSEG0CHINDEX=0
    VHTSEG1CHINDEX=0
    DRIVER=nl80211
    NO_VIRT=0 # not use virtual interface
    COUNTRY=
    FREQ_BAND=2.4
    NO_HAVEGED=0
    HOSTAPD_DEBUG_ARGS=
    USE_PSK=0
    ISOLATE_CLIENTS=0
    QR=0 # show wifi qr
    
    # script variables
    VWIFI_IFACE=  # virtual wifi interface name, if created
    VIRT_NAME= # name to use for virtual interface if --virt-name is used
    AP_IFACE=     # can be VWIFI_IFACE or WIFI_IFACE
    USE_IWCONFIG=0  # some device can't use iw
    #######
    
    #-- to deal with info of a running instance. then will exit
    LIST_RUNNING=0
    STOP_ID=
    LIST_CLIENTS_ID=

    # -- variables for running
    CONFDIR=
    IP_VERs=
    NM_UNM_LIST=  # it's called "list" but for now one interface
    NM_PID=
    FIREWALLD_PID=
    TMP_FIREWALLD_ZONE=
}

parse_user_options(){
    while [[ -n "$1" ]]; do
        case "$1" in
            -h|--help)
                usage
                exit 0
                ;;
            --version)
                echo "$VERSION"
                exit 0
                ;;
            -i)
                shift
                CONN_IFACE="$1"
                shift
                ;;
            -o)
                shift
                INTERNET_IFACE="$1"
                shift
                ;;
            -n)
                shift
                SHARE_METHOD=none
                ;;
            --ban-priv)
                shift
                BANLAN=1
                ;;
            --tp)
                shift
                TP_PORT="$1"
                SHARE_METHOD=redsocks
                shift
                ;;
                
                
            -g)
                shift
                GATEWAY4="$1"
                shift
                ;;
            -6)
                shift
                IPV6=1
                ;;
            --no4)
                shift
                NO4=1
                ;;
            --p6)
                shift
                PREFIX6="$1"
                IPV6=1
                shift
                ;;
            --mac)
                shift
                NEW_MACADDR="$1"
                shift
                ;;
            --random-mac)
                shift
                MAC_USE_RANDOM=1
                ;;
                
            --dns)
                shift
                DNS="$1"
                shift
                ;;
            --no-dns)
                shift
                dnsmasq_NO_DNS=1
                ;;
            --no-dnsmasq)
                shift
                NO_DNSMASQ=1
                ;;
            --dhcp-dns)
                shift
                DHCP_DNS="$1"
                shift
                ;;
            --dhcp-dns6)
                shift
                DHCP_DNS6="$1"
                shift
                ;;
            --catch-dns)
                shift
                CATCH_DNS=1
                ;;    
            --log-dns)
                shift
                SHOW_DNS_QUERY=1
                ;;
            --hostname)
                shift
                THISHOSTNAME="$1"
                shift
                ;;
            -d)
                shift
                ETC_HOSTS=1
                ;;
            -e)
                shift
                ADDN_HOSTS="$1"
                shift
                ;;
            --dns-nocache)
                shift
                DNS_NOCACHE=1
                ;;
            
            --isolate-clients)
                shift
                ISOLATE_CLIENTS=1
                ;;
                
            --ap)
                shift
                WIFI_IFACE="$1"
                shift
                SSID="$1"
                shift
                ;;
            -p|--password)
                shift
                PASSPHRASE="$1"
                shift
                ;;
            --qr)
                shift
                QR=1
                ;;
                
                
            --hidden)
                shift
                HIDDEN=1
                ;;
            --mac-filter)
                shift
                MAC_FILTER=1
                ;;
            --mac-filter-accept)
                shift
                MAC_FILTER_ACCEPT="$1"
                shift
                ;;

            -c)
                shift
                CHANNEL="$1"
                shift
                ;;
            --hs20)
                shift
                HOTSPOT20=1
                ;;
            -w)
                shift
                WPA_VERSION="$1"
                [[ "$WPA_VERSION" == "2+1" ]] && WPA_VERSION=1+2
                shift
                ;;

            --wifi4|--ieee80211n)
                shift
                IEEE80211N=1
                ;;
            --req-ht|--require-ht)
                shift
                REQUIREHT=1
                ;;
            --wifi5|--ieee80211ac)
                shift
                IEEE80211AC=1
                ;;
            --req-vht|--require-vht)
                shift
                REQUIREVHT=1
                ;;
            --ht-capab)
                shift
                HT_CAPAB="$1"
                shift
                ;;
            --vht-capab)
                shift
                VHT_CAPAB="$1"
                shift
                ;;
            --vht-ch-width|--vht-channel-width)
                shift
                VHTCHANNELWIDTH="$1"
                shift
                ;;
            --vht-seg0-ch|--vht-seg0-channel)
                shift
                VHTSEG0CHINDEX="$1"
                shift
                ;;
            --vht-seg1-ch|--vht-seg1-channel)
                shift
                VHTSEG1CHINDEX="$1"
                shift
                ;;
            --driver)
                shift
                DRIVER="$1"
                shift
                ;;
            --no-virt)
                shift
                NO_VIRT=1
                ;;
            --virt-name)
                shift
                VIRT_NAME="$1"
                shift
                ;;

            --country)
                shift
                COUNTRY="$1"
                shift
                ;;
            --freq-band)
                shift
                FREQ_BAND="$1"
                shift
                ;;
            --no-haveged)
                shift
                NO_HAVEGED=1
                ;;
            --hostapd-debug)
                shift
                if [ "$1" = "1" ]; then
                    HOSTAPD_DEBUG_ARGS="-d"
                elif [ "$1" = "2" ]; then
                    HOSTAPD_DEBUG_ARGS="-dd"
                else
                    printf "Error: argument for --hostapd-debug expected 1 or 2, got %s\n" "$1"
                    exit 1
                fi
                shift
                ;;
            --psk)
                shift
                USE_PSK=1
                ;;

            --daemon)
                shift
                DAEMONIZE=1
                ;;
            --stop)
                shift
                STOP_ID="$1"
                shift
                ;;
            -l|--list-running)
                shift
                LIST_RUNNING=1
                ;;
            --lc|--list-clients)
                shift
                LIST_CLIENTS_ID="$1"
                shift
                ;;

            *)
                echo  "Invalid parameter: $1" 1>&2
                exit 1
                ;;
        esac
    done
}


# seperate ip and port
sep_ip_port() {
    # usage: sep_ip_port <ip:port> <var for ip> <var for port>
    # input <ip:port> can be:
    #   port (ip is 127.0.0.1)
    #   ipv4
    #   [ipv6]
    #   ipv4:port
    #   [ipv6]:port
    local IP
    local PORT
    local INPUT
    INPUT="$1"
    if (echo "$INPUT" | grep '\.' >/dev/null 2>&1) ;then  
        if (echo "$INPUT" | grep ':' >/dev/null 2>&1) ;then
            # ipv4 + port
            IP="$(echo "$INPUT" | cut -d: -f1)"
            PORT="$(echo "$INPUT" | cut -d: -f2)"
        else
            # ipv4
            IP="$INPUT"
        fi
    elif (echo "$INPUT" | grep '\]' >/dev/null 2>&1) ;then 
        if (echo "$INPUT" | grep '\]\:' >/dev/null 2>&1) ;then
            # ipv6 + port
            IP="$(echo "$INPUT" | cut -d']' -f1 | cut -d'[' -f2)"
            PORT="$(echo "$INPUT" | cut -d']' -f2 |cut -d: -f2)"
        else
            # ipv6
            IP="$(echo "$INPUT" | cut -d']' -f1 | cut -d'[' -f2)"
        fi
    else 
        # port
        IP='127.0.0.1'
        PORT="$INPUT"
    fi
    printf -v "$2" %s "$IP"
    printf -v "$3" %s "$PORT"
}

#=========================
is_interface() {
    [[ -z "$1" ]] && return 1
    [[ -d "/sys/class/net/${1}" ]]
}

is_vface_name_allocated(){
    is_interface "$1" || [[ -f "$COMMON_CONFDIR/vfaces/${1}" ]]
}

get_interface_phy_device() { # only for wifi interface
    local x
    for x in /sys/class/ieee80211/*; do
        [[ ! -e "$x" ]] && continue
        if [[ "${x##*/}" = "$1" ]]; then
            echo "$1"
            return 0
        elif [[ -e "$x/device/net/$1" ]]; then
            echo "${x##*/}"
            return 0
        elif [[ -e "$x/device/net:$1" ]]; then
            echo "${x##*/}"
            return 0
        fi
    done
    echo "Failed to get phy interface" >&2
    return 1
}

get_adapter_info() { # only for wifi interface
    local iPHY
    iPHY=$(get_interface_phy_device "$1")
    [[ $? -ne 0 ]] && return 1
    iw phy "$iPHY" info
}

get_adapter_kernel_module() {
    local MODULE
    MODULE=$(readlink -f "/sys/class/net/$1/device/driver/module")
    echo "${MODULE##*/}"
}

can_be_sta_and_ap() {
    # iwconfig does not provide this information, assume false
    [[ $USE_IWCONFIG -eq 1 ]] && return 1
    if [[ "$(get_adapter_kernel_module "$1")" == "brcmfmac" ]]; then
        echo "WARN: brmfmac driver doesn't work properly with virtual interfaces and" >&2
        echo "      it can cause kernel panic. For this reason we disallow virtual" >&2
        echo "      interfaces for your adapter." >&2
        echo "      For more info: https://github.com/oblique/create_ap/issues/203" >&2
        return 1
    fi
    get_adapter_info "$1" | grep -E '{.* managed.* AP.*}' > /dev/null 2>&1 && return 0
    get_adapter_info "$1" | grep -E '{.* AP.* managed.*}' > /dev/null 2>&1 && return 0
    return 1
}

can_be_ap() {
    # iwconfig does not provide this information, assume true
    [[ $USE_IWCONFIG -eq 1 ]] && return 0
    get_adapter_info "$1" | grep -E '\* AP$' > /dev/null 2>&1 && return 0
    return 1
}

can_transmit_to_channel() {
    local IFACE CHANNEL_NUM CHANNEL_INFO
    IFACE=$1
    CHANNEL_NUM=$2

    if [[ $USE_IWCONFIG -eq 0 ]]; then
        CHANNEL_INFO=$(get_adapter_info "${IFACE}" | grep -E " [0-9]+(\.[0-9]+){0,1} MHz \[${CHANNEL_NUM}\]")
        [[ -z "${CHANNEL_INFO}" ]] && return 1
        [[ "${CHANNEL_INFO}" == *no\ IR* ]] && return 1
        [[ "${CHANNEL_INFO}" == *disabled* ]] && return 1
        return 0
    else
        CHANNEL_NUM=$(printf '%02d' ${CHANNEL_NUM})
        CHANNEL_INFO=$(iwlist "${IFACE}" channel | grep -E "Channel[[:blank:]]${CHANNEL_NUM}[[:blank:]]?:")
        [[ -z "${CHANNEL_INFO}" ]] && return 1
        return 0
    fi
}

# taken from iw/util.c
ieee80211_frequency_to_channel() {
    local FREQ=$1
    if [[ $FREQ -eq 2484 ]]; then
        echo 14
    elif [[ $FREQ -lt 2484 ]]; then
        echo $(( ($FREQ - 2407) / 5 ))
    elif [[ $FREQ -ge 4910 && $FREQ -le 4980 ]]; then
        echo $(( ($FREQ - 4000) / 5 ))
    elif [[ $FREQ -le 45000 ]]; then
        echo $(( ($FREQ - 5000) / 5 ))
    elif [[ $FREQ -ge 58320 && $FREQ -le 64800 ]]; then
        echo $(( ($FREQ - 56160) / 2160 ))
    else
        echo 0
    fi
}


is_interface_wifi_connected() {
    if [[ $USE_IWCONFIG -eq 0 ]]; then
        iw dev "$1" link 2>&1 | grep -E '^Connected to' > /dev/null 2>&1 && return 0
    else
        iwconfig "$1" 2>&1 | grep -E 'Access Point: [0-9a-fA-F]{2}:' > /dev/null 2>&1 && return 0
    fi
    return 1
}


is_unicast_macaddr() {
    local x
    x=$(echo "$1" | cut -d: -f1)
    x=$(printf '%d' "0x${x}")
    [[ $(expr $x % 2) -eq 0 ]]
}

get_interface_mac() {
    is_interface "$1" || return
    cat "/sys/class/net/${1}/address"
}

show_interface_pci_info() {  # pci id / model / virtual
    is_interface "$1" || return
    
    local device_path
    local bus_id=""
    local device_type_and_bus_id="unknown"
    local driver=""
    local device_fullname=""
    
    device_path="$(readlink -f /sys/class/net/$1)"
    
    if [[ "$device_path" == "/sys/devices/pci"* ]]; then
        local pci_path

        pci_path=$device_path/../..
        
        if [[ -d "$pci_path/driver" ]] ; then
            driver=$(readlink -f "$pci_path/driver" | sed 's/\//\n/g' | tail -n 1)
        fi
        
        bus_id="$(echo "$device_path" | sed 's/\//\n/g' | tail -n 3 |sed -n 1p)"
        device_type_and_bus_id="PCI: $bus_id"
        
        if which lspci >/dev/null 2>&1 ; then
            device_fullname="$( lspci -D -nn -s "$bus_id" | awk '{$1="" ; print $0}' )"
        fi
        
    elif [[ "$device_path" == *"/virtual/"* ]]; then
        device_type_and_bus_id="virtual interface"
    fi
    
    echo "$device_type_and_bus_id"
    [[ -n "$driver" ]] && echo "System-already-loaded driver: $driver"
    [[ -n "$device_fullname" ]] && echo "$device_fullname"
    echo ""
    # TODO usb
}

alloc_new_vface_name() { # only for wifi
    local i=0
    local v_iface_name="$VIRT_NAME"
    if [[ -z $VIRT_NAME ]]; then
        while :; do
            v_iface_name="x$i${WIFI_IFACE}"
            i=$((i + 1))
            is_vface_name_allocated "${v_iface_name}" || break
        done
    fi
    mkdir -p "$COMMON_CONFDIR/vfaces"
    touch "$COMMON_CONFDIR/vfaces/${v_iface_name}"
    echo "${v_iface_name}"
}

dealloc_vface_name() {
    rm -f "$COMMON_CONFDIR/vfaces/$1"
}

#======

get_all_mac_in_system() {
    cat /sys/class/net/*/address
}

get_new_macaddr_according_to_existing() {
    local REALDEV OLDMAC NEWMAC LAST_BYTE i
    REALDEV=$1
    OLDMAC=$(get_interface_mac "$REALDEV")
    NEWMAC=""
    LAST_BYTE=$(printf %d 0x${OLDMAC##*:})
    for i in {10..240}; do
        NEWMAC="${OLDMAC%:*}:$(printf %02x $(( ($LAST_BYTE + $i) % 256 )))"
        (get_all_mac_in_system | grep "$NEWMAC" > /dev/null 2>&1) || break
    done
    echo "$NEWMAC"
}

generate_random_mac() {
    local r1 r2 r3 r4 r5 r6 
    local RAND_MAC
    while :; do
        r1=$( printf "%02x" $(($RANDOM%256/4*4)) )
        r2=$( printf "%02x" $(($RANDOM%256)) )
        r3=$( printf "%02x" $(($RANDOM%256)) )
        r4=$( printf "%02x" $(($RANDOM%256)) )
        r5=$( printf "%02x" $(($RANDOM%256)) )
        r6=$( printf "%02x" $(($RANDOM%256)) )
        RAND_MAC="$r1:$r2:$r3:$r4:$r5:$r6"
        ( ! ip link | grep "link" | grep "$RAND_MAC" > /dev/null 2>&1 ) && \
        ( ! ip maddress | grep "link" | grep "$RAND_MAC" > /dev/null 2>&1 ) && \
        ( ! ip neigh | grep "lladdr $RAND_MAC" > /dev/null 2>&1 ) && \
        ( ! get_all_mac_in_system | grep "$RAND_MAC" ) && \
        break
    done
    echo "$RAND_MAC"
}


is_ip4_lan_range_available() { # checks 192.168.x.x
    ( ip -4 address | grep "inet 192\.168\.$1\." > /dev/null 2>&1 ) && return 1
    ( ip -4 route | grep "^192\.168\.$1\." > /dev/null 2>&1 ) && return 1
    ( ip -4 route get 192.168.$1.0 2>&1 | grep -E "\bvia\b|\bunreachable\b" > /dev/null 2>&1 ) && \
    ( ip -4 route get 192.168.$1.255 2>&1 | grep  -E "\bvia\b|\bunreachable\b" > /dev/null 2>&1 )  && return 0
    return 1
}
is_ip6_lan_range_available() {  # checks fdxx::
    ( ip -6 address | grep -i "inet6 fd$1:$2$3:$4$5:$6$7:" > /dev/null 2>&1 ) && return 1
    ( ip -6 route | grep -i "^fd$1:$2$3:$4$5:$6$7:" > /dev/null 2>&1 ) && return 1
    ( ip -6 route get fd$1:$2$3:$4$5:$6$7:: 2>&1 | grep -E "\bvia\b|\bunreachable\b" > /dev/null 2>&1 ) && \
    ( ip -6 route get fd$1:$2$3:$4$5:$6$7:ffff:ffff:ffff:ffff 2>&1 | grep -E "\bvia\b|\bunreachable\b" > /dev/null 2>&1 )  && return 0
    return 1
}

generate_random_ip4() {
    local random_ip4
    while :; do
        random_ip4=$(($RANDOM%256))
        is_ip4_lan_range_available $random_ip4 && break
    done
    echo "192.168.$random_ip4.1"
}
generate_random_lan_ip6_prefix() {
    local r1 r2 r3 r4 r5 r6 r7
    while :; do
        r1=$( printf "%x" $(($RANDOM%240+16)) )
        r2=$( printf "%x" $(($RANDOM%240+16)) )
        r3=$( printf "%x" $(($RANDOM%240+16)) )
        r4=$( printf "%x" $(($RANDOM%240+16)) )
        r5=$( printf "%x" $(($RANDOM%240+16)) )
        r6=$( printf "%x" $(($RANDOM%240+16)) )
        r7=$( printf "%x" $(($RANDOM%240+16)) )
        is_ip6_lan_range_available "$r1" "$r2" "$r3" "$r4" "$r5" "$r6" "$r7" && break
    done
    echo "fd$r1:$r2$r3:$r4$r5:$r6$r7::"
}



# start haveged when needed
haveged_watchdog() {
    local show_warn=1
    while :; do
        if [[ $(cat /proc/sys/kernel/random/entropy_avail) -lt 1000 ]]; then
            if ! which haveged > /dev/null 2>&1; then
                if [[ $show_warn -eq 1 ]]; then
                    echo "WARN: Low entropy detected. We recommend you to install \`haveged'" 1>&2
                    show_warn=0
                fi
            elif ! pidof haveged > /dev/null 2>&1; then # TODO judge zombie ?
                echo "Low entropy detected, starting haveged" 1>&2
                # boost low-entropy
                haveged -w 1024 -p $COMMON_CONFDIR/haveged.pid
            fi
        fi
        sleep 2
    done
}
pid_watchdog() {
    local PID="$1"
    local SLEEP="$2"
    local ERR_MSG="$3"
    local ST
    while true
    do 
        if [[ -e "/proc/$PID" ]]; then
            ST="$(cat "/proc/$PID/status" | grep "^State:" | awk '{print $2}')"
            if [[ "$ST" != 'Z' ]]; then
                sleep "$SLEEP"
                continue
            fi
        fi
        die "$ERR_MSG"
    done
    
}
#========
get_pid_by_dbus_name() {
    local DBUS_NAME="$1"
    local pid  r
    
    which dbus-send >/dev/null 2>&1 || return 1
    
    pid="$( dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetConnectionUnixProcessID string:$DBUS_NAME  2>/dev/null | grep "   uint32 " | awk '{print $2}' )"
    r=$?
    
    echo "$pid"
    return $r
}
is_same_netns() {
    local pid2="$1"
    [[ ! -f /proc/$$/ns/net ]] && return 0 # no netns feature. treat as same
    [[ "$(readlink /proc/$$/ns/net)" == "$(readlink /proc/$pid2/ns/net)" ]] && return 0
    return 1
}
#-----------------
# only support NetworkManager >= 0.9.9
is_nm_running() {
    NM_PID="$(get_pid_by_dbus_name "org.freedesktop.NetworkManager")"
    
    [[ ! -n "$NM_PID" ]] && return 1 # not running
    
    if (which nmcli >/dev/null 2>&1 ) && (nmcli -t -f RUNNING g 2>&1 | grep -E '^running$' >/dev/null 2>&1 ) ; then
        if is_same_netns "$NM_PID"; then
            return 0
        fi
    fi
    
    NM_PID= # cancel value if treat as not running
    return 1  # not running
}

nm_knows() {
    (nmcli dev show "$1" | grep -E "^GENERAL.STATE:" >/dev/null 2>&1 ) && return 0 # nm sees
    return 1 # nm doesn't see this interface
}
nm_get_manage() { # get an interface's managed state
    local s
    s=$(nmcli dev show "$1" | grep -E "^GENERAL.STATE:") || return 2 # no such interface
    (echo "$s" | grep "unmanaged" >/dev/null 2>&1) && return 1 # unmanaged
    return 0 # managed
}
nm_set_unmanaged() {
    while ! nm_knows "$1" ; do # wait for virtual wifi interface seen by NM
        sleep 0.5
    done
    if nm_get_manage "$1" ;then
        echo "Set $1 unmanaged by NetworkManager"
        nmcli dev set "$1" managed no || die "Failed to set $1 unmanaged by NetworkManager"
        NM_UNM_LIST=$1
        sleep 1
    fi
}

nm_set_managed() {
    nmcli dev set "$1" managed yes
    NM_UNM_LIST=
}
nm_restore_manage() {
    if [[ $NM_UNM_LIST ]]; then
        echo "Restore $NM_UNM_LIST managed by NetworkManager"
        nm_set_managed "$NM_UNM_LIST"
        sleep 0.5
    fi
}
#-------
is_firewalld_running() {
    FIREWALLD_PID="$(get_pid_by_dbus_name "org.fedoraproject.FirewallD1")"
    
    [[ ! -n "$FIREWALLD_PID" ]] && return 1 # not running
    
    if (which firewall-cmd >/dev/null 2>&1 ) && [[ "$(firewall-cmd --state 2>&1)" == "running" ]] ; then
        if is_same_netns "$FIREWALLD_PID"; then
            echo "firewalld is running ($(firewall-cmd --version))"
            return 0
        fi
    fi

    FIREWALLD_PID=  # cancel value if treat as not running
    return 1  # not running
}
firewalld_add_tmpzone() {
#     TMP_FIREWALLD_ZONE="lrt${$}${SUBNET_IFACE}"
    TMP_FIREWALLD_ZONE="trusted"
#     firewall-cmd --new-zone=$TMP_FIREWALLD_ZONE  || die "Failed creating temporary firewalld zone"
    echo "Adding $SUBNET_IFACE to firewalld '$TMP_FIREWALLD_ZONE' zone"
    firewall-cmd --zone=$TMP_FIREWALLD_ZONE --add-interface=$SUBNET_IFACE >/dev/null || die "Failed adding interface to firewalld temporary zone"
}
firewalld_del_tmpzone() {
    if [[ -n "$TMP_FIREWALLD_ZONE" ]];then
        echo "Removing $SUBNET_IFACE from firewalld '$TMP_FIREWALLD_ZONE' zone"
        firewall-cmd --zone=$TMP_FIREWALLD_ZONE --remove-interface=$SUBNET_IFACE >/dev/null
#         firewall-cmd --delete-zone=$TMP_FIREWALLD_ZONE
    fi
}

#=========
CUSTOM_CHAINS_4_filter=
CUSTOM_CHAINS_4_nat=
CUSTOM_CHAINS_6_filter=
CUSTOM_CHAINS_6_nat=
iptb() 
{
    local FoS=$1 # 4 | 6
    shift
    local Vis=$1 # 'v' | 'n'
    shift
    local T=$1 # table
    shift
    local ACT=$1 # action: I | A | N  .  On undo: I or A -> D , N -> F+X
    shift
    local CH=$1 # chain
    shift
    
    [[ "$IPV6" -ne 1 && "$FoS" == "6" ]] && return
    
    local CMD_HEAD=""
    local MOUTH=""
    local NECK=""
    local HAND_UN_NC=0
    local TAIL=""
    
    local FULL=""
    local ADD_TO_UNDO=1
    
    local arr_name  w
    
    for arr_name in CUSTOM_CHAINS_4_filter CUSTOM_CHAINS_4_nat CUSTOM_CHAINS_6_filter CUSTOM_CHAINS_6_nat
    do
        local arr_content
        eval arr_content=\"\${$arr_name}\"
        #echo $arr_content
        
        for w in  $arr_content
        do
            if [[ "$arr_name" =~ "$FoS" && "$arr_name" =~ "$T" && "$w" == "$CH" ]]; then
                ADD_TO_UNDO=0
            fi
        done
    done
    

    [[ "$FoS" == "4" ]] && CMD_HEAD="iptables -w "
    [[ "$FoS" == "6" ]] && CMD_HEAD="ip6tables -w "
    
    [[ "$Vis" == 'v' ]] && MOUTH="-v"
    
    NECK="-t ${T}"
    
    if [[ "$ACT" == "N" ]]; then
        eval CUSTOM_CHAINS_${FoS}_${T}=\"\${CUSTOM_CHAINS_${FoS}_${T}} ${CH}\"
        HAND_UN_NC=1
    fi
    
    
    
    [[ ! "$NETFILTER_XT_MATCH_COMMENT" == "0" ]] && TAIL="-m comment --comment lrt${$}${SUBNET_IFACE}"
    
    if [[ "$ADD_TO_UNDO" -eq 1 ]]; then
        if [[ "$ACT" == "I" || "$ACT" == "A" ]]; then
            echo "$CMD_HEAD $NECK -D ${CH} $@ $TAIL" >> $CONFDIR/undo_iptables.sh 
        fi
        
        if [[ "$HAND_UN_NC" -eq 1 ]]; then
            echo "$CMD_HEAD $NECK -F ${CH} $@ $TAIL" >> $CONFDIR/undo_iptables_2.sh
            echo "$CMD_HEAD $NECK -X ${CH} $@ $TAIL" >> $CONFDIR/undo_iptables_2.sh
        fi
    fi
    
 
    

    FULL="$CMD_HEAD $MOUTH $NECK -${ACT} ${CH} $@ $TAIL"
    #echo $FULL
    $FULL
    return $?
}

disable_unwanted_forwarding() {
    for iv in "${IP_VERs[@]}"; do
        if [[ "$INTERNET_IFACE" ]]; then
            iptb "$iv" n filter I FORWARD  \
                -i "$SUBNET_IFACE"      ! -o "$INTERNET_IFACE" \
                -j REJECT || die
            iptb "$iv" n filter I FORWARD  \
                ! -i "$INTERNET_IFACE"    -o "$SUBNET_IFACE" \
                -j REJECT || die
        fi
        
        if [[ "$SHARE_METHOD" == 'redsocks' || "$SHARE_METHOD" == 'none' \
            || ( "$iv" -eq "4" && "$NO4" -eq 1  ) ]];then
            iptb "$iv" n filter I FORWARD  -i "$SUBNET_IFACE"  -j REJECT || die
            iptb "$iv" n filter I FORWARD  -o "$SUBNET_IFACE"  -j REJECT || die
        fi
    done
        
}
start_nat() {
    local SUBNET_NET
    
    local iv

    echo
    echo "iptables: NAT "
    
    for iv in "${IP_VERs[@]}"; do
        [[ "$iv" -eq "4" && ! $NO4 -eq 0 ]] && continue
        
        [[ "$iv" -eq "4" ]] && SUBNET_NET="$SUBNET_NET4"
        [[ "$iv" -eq "6" ]] && SUBNET_NET="$SUBNET_NET6"
        
        if [[ -n "$INTERNET_IFACE" ]]; then # only one Internet interface
            # masquerade subnet -> internet
            iptb "$iv" v nat I POSTROUTING -s "$SUBNET_NET"  ! -d "$SUBNET_NET" \
                -o "$INTERNET_IFACE" \
                -j MASQUERADE || die
                
            # forward subnet -> internet
            iptb "$iv" v filter I FORWARD  -i "$SUBNET_IFACE"  -s "$SUBNET_NET" \
                -o $INTERNET_IFACE \
                -j ACCEPT || die
            
            # forward any -> subnet
            iptb "$iv" v filter I FORWARD  -o "$SUBNET_IFACE"  -d "$SUBNET_NET"  \
                -i "$INTERNET_IFACE" \
                -j ACCEPT || die
        else # any interface can be Internet
            # masquerade subnet -> any(!subnet)
            iptb "$iv" v nat I POSTROUTING -s "$SUBNET_NET"  ! -d "$SUBNET_NET" \
                ! -o "$SUBNET_IFACE"  \
                -j MASQUERADE || die
                
            # forward subnet -> any
            iptb "$iv" v filter I FORWARD  -i "$SUBNET_IFACE"  -s "$SUBNET_NET"  \
                -j ACCEPT || die
                
            # forward any -> subnet
            iptb "$iv" v filter I FORWARD  -o "$SUBNET_IFACE"  -d "$SUBNET_NET" \
                -j ACCEPT || die
        fi
    done 
}

start_ban_lan() {
    local arr_nets_to_protect
    local ICMP_NAME
    local iv  s
    
    echo
    echo "iptables: Disallow clients to access LAN"
    
    for iv in "${IP_VERs[@]}"; do
        # ban forwarding for subnet
        iptb "$iv" n filter N lrt${$}${SUBNET_IFACE}-BLF || die
        # TODO: allow '--dhcp-dns(6)' address port 53, which can be something needed, e.g. a VPN's internal private IP
        if [[ "$iv" -eq "4" ]]; then
            arr_nets_to_protect=("0.0.0.0/8" "10.0.0.0/8" "100.64.0.0/10" "127.0.0.0/8" "169.254.0.0/16" "172.16.0.0/12" "192.168.0.0/16" "224.0.0.0/4" "255.255.255.255") 
            ICMP_NAME="icmp"
        elif [[ "$iv" -eq "6" ]]; then
            arr_nets_to_protect=("fc00::/7" "fe80::/10" "ff00::/8" "::1" "::/128" "::ffff:0:0/96" "::ffff:0:0:0/96")
            ICMP_NAME="icmpv6"
        fi
        for s in "${arr_nets_to_protect[@]}"; do
            iptb "$iv" v filter I lrt${$}${SUBNET_IFACE}-BLF -d "$s" -j REJECT || die
        done
        iptb "$iv" n filter I FORWARD -i ${SUBNET_IFACE} -j lrt${$}${SUBNET_IFACE}-BLF || die
        
        # ban input from subnet
        iptb "$iv" n filter N lrt${$}${SUBNET_IFACE}-BLI || die
        iptb "$iv" v filter I lrt${$}${SUBNET_IFACE}-BLI -i ${SUBNET_IFACE} ! -p "$ICMP_NAME" -j REJECT || die # ipv6 need icmp to function. TODO: maybe we can block some unneeded icmp to improve security
        iptb "$iv" n filter I INPUT -i ${SUBNET_IFACE} -j lrt${$}${SUBNET_IFACE}-BLI || die
    done

}

allow_dns_port() {
    local SUBNET_NET
    local GATEWAY
    local PROTs
    local iv  pt

    
    echo
    echo "iptables: allow DNS"
    
    for iv in "${IP_VERs[@]}"; do
        [[ "$iv" -eq "4" ]] && GATEWAY="$GATEWAY4"
        [[ "$iv" -eq "6" ]] && GATEWAY="$GATEWAY6"
        
        [[ "$iv" -eq "4" ]] && SUBNET_NET="$SUBNET_NET4"
        [[ "$iv" -eq "6" ]] && SUBNET_NET="$SUBNET_NET6"


        PROTs=("tcp" "udp")
        for pt in "${PROTs[@]}"; do
            iptb "$iv" v filter I INPUT -i "$SUBNET_IFACE" -s "$SUBNET_NET" -d "$GATEWAY" -p "$pt" -m "$pt" --dport 53 -j ACCEPT || die
        done
    done 
}


start_catch_dns() {
    local GATEWAY
    local PROTs
    local iv  pt

    echo
    echo "iptables: redirect DNS queries to this host"
    
    for iv in "${IP_VERs[@]}"; do
        [[ "$iv" -eq "4" ]] && GATEWAY="$GATEWAY4"
        [[ "$iv" -eq "6" ]] && GATEWAY="$GATEWAY6"
   
        PROTs=("tcp" "udp")
        for pt in "${PROTs[@]}"; do
            iptb "$iv" v nat I PREROUTING -i "$SUBNET_IFACE" ! -d "$GATEWAY" -p "$pt" -m "$pt" --dport 53 -j REDIRECT --to-ports 53 || die
        done
    done
}


allow_dhcp() {
    echo 
    echo "iptables: allow dhcp"
    
    iptb 4 v filter I INPUT -i ${SUBNET_IFACE} -p udp -m udp --dport 67 -j ACCEPT || die
    iptb 6 v filter I INPUT -i ${SUBNET_IFACE} -p udp -m udp --dport 547 -j ACCEPT || die
}

# TODO: use 'DNAT' instead of '--to-ports' to support other IP
start_redsocks() {
    local SUBNET_NET
    local arr_nets_to_ignore
    local s iv
    
    echo
    echo "iptables: transparent proxy non-LAN TCP and UDP(not tested) traffic to port ${TP_PORT}"
    
    for iv in "${IP_VERs[@]}"; do
        [[ "$iv" -eq "4" && ! $NO4 -eq 0 ]] && continue
        
        [[ "$iv" -eq "4" ]] && SUBNET_NET="$SUBNET_NET4"
        [[ "$iv" -eq "6" ]] && SUBNET_NET="$SUBNET_NET6"
            
            
        iptb "$iv" n nat N lrt${$}${SUBNET_IFACE}-TP || die
        
        if [[ "$iv" -eq "4" ]]; then
            arr_nets_to_ignore=("0.0.0.0/8" "10.0.0.0/8" "100.64.0.0/10" "127.0.0.0/8" "169.254.0.0/16" "172.16.0.0/12" "192.168.0.0/16" "224.0.0.0/4" "255.255.255.255")
        elif [[ "$iv" -eq "6" ]];then
            arr_nets_to_ignore=("fc00::/7" "fe80::/10" "ff00::/8" "::1" "::")
        fi
        
        for s in "${arr_nets_to_ignore[@]}"; do
            iptb "$iv" n nat A lrt${$}${SUBNET_IFACE}-TP -d "$s" -j RETURN || die
        done

        iptb "$iv" v nat A lrt${$}${SUBNET_IFACE}-TP -p tcp -j REDIRECT --to-ports ${TP_PORT} || die
        iptb "$iv" v nat A lrt${$}${SUBNET_IFACE}-TP -p udp -j REDIRECT --to-ports ${TP_PORT} || die

        iptb "$iv" v nat I PREROUTING -i "$SUBNET_IFACE" -s "$SUBNET_NET" -j lrt${$}${SUBNET_IFACE}-TP || die

        
        iptb "$iv" v filter I INPUT -i "$SUBNET_IFACE" -s "$SUBNET_NET" -p tcp -m tcp --dport ${TP_PORT}  -j ACCEPT || die
        iptb "$iv" v filter I INPUT -i "$SUBNET_IFACE" -s "$SUBNET_NET" -p udp -m udp --dport ${TP_PORT}  -j ACCEPT || die
    done
}

#---------------------------------------
backup_ipv6_bits() {
    mkdir "$CONFDIR/sys_6_conf_iface" || die "Failed making dir to save interface IPv6 status"
    cp  "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/disable_ipv6" \
        "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/accept_ra"     \
        "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/use_tempaddr"  \
        "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/addr_gen_mode" \
            "$CONFDIR/sys_6_conf_iface/" || die "Failed backing up interface ipv6 bits"
            
    if [[ "$SHARE_METHOD" == 'redsocks' ]] ; then
        cp "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/forwarding" \
            "$CONFDIR/sys_6_conf_iface/" || die "Failed backking up interface ipv6 bits"
    fi
}
set_ipv6_bits() {
    if [[ $IPV6 -eq 1 ]]; then
        echo 0 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/disable_ipv6"
        echo 0 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/accept_ra"
        echo 0 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/use_tempaddr"
        echo 0 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/addr_gen_mode"
    else
        echo 1 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/disable_ipv6"
    fi
}
restore_ipv6_bits() {
    if [[ -d "$CONFDIR/sys_6_conf_iface" ]]; then
        cp -f "$CONFDIR/sys_6_conf_iface/*" "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/"
    fi
}

set_interface_mac() {
    local INTERFACE
    local MAC
    
    INTERFACE=$1
    MAC=$2
    
    ip link set dev "${INTERFACE}" address "${MAC}" 
}

backup_interface_status() {
    # virtual wifi interface will be destroyed, so no need to save status
    
    # backup interface up or down status
    (ip link show "${SUBNET_IFACE}" |grep -q "state UP") && SUBNET_IFACE_ORIGINAL_UP_STATUS=1
    
    # save interface old mac 
    #if [[ -n "$NEW_MACADDR" ]]; then 
        OLD_MACADDR=$(get_interface_mac "$SUBNET_IFACE")
        #echo "Saved ${SUBNET_IFACE} old MAC address ${OLD_MACADDR} into RAM"
    #fi
    
    backup_ipv6_bits
    
    # TODO : ? backup ip and others???
    
    # nm managing status is saved when nm_set_unmanaged()
}
restore_interface_status() {
    # virtual wifi interface will be destroyed, so no need to restore status
    # don't use [[ $VWIFI_IFACE ]] to judge, if creating virtual wifi failed, VWIFI_IFACE is empty
    [[ "$WIFI_IFACE" && "$NO_VIRT" -eq 0 ]] && return
    
    restore_ipv6_bits

    if [[ -n "$OLD_MACADDR" && "$(get_interface_mac "$SUBNET_IFACE")" != "$OLD_MACADDR" ]] ; then
        echo "Restoring ${SUBNET_IFACE} to old MAC address ${OLD_MACADDR} ..."
        set_interface_mac "${SUBNET_IFACE}" "${OLD_MACADDR}" || echo "Failed restoring ${SUBNET_IFACE} to old MAC address ${OLD_MACADDR}" >&2
    fi
    
    nm_restore_manage
    
    [[ $SUBNET_IFACE_ORIGINAL_UP_STATUS -eq 1 ]] && ip link set up dev "${SUBNET_IFACE}" && echo "Restore ${SUBNET_IFACE} to link up"
}
#---------------------------------------

kill_processes() { # for this instance
    #echo "Killing processes"
    local x  pid
    for x in $CONFDIR/*.pid; do
        # even if the $CONFDIR is empty, the for loop will assign
        # a value in $x. so we need to check if the value is a file
        if [[ -f $x ]] &&  sleep 0.3  && [[ -f $x ]]; then
            pid=$(cat "$x")
            pn=$( ps -p "$pid" -o comm= ) 
            #echo "Killing $pid $pn ... "
            pkill -P "$pid"
            kill "$pid" 2>/dev/null && ( echo "Killed $(basename "$x") $pid $pn" && rm "$x" ) || echo "Failed to kill $(basename "$x") $pid $pn, it may have exited"
        fi
    done
}

_cleanup() {
    local x

    ip addr flush "${SUBNET_IFACE}"
    
    rm -rf "$CONFDIR"
    
    ip link set down dev "${SUBNET_IFACE}"
    
    firewalld_del_tmpzone
    
    if [[ $VWIFI_IFACE ]]; then # the subnet interface (virtual wifi interface) will be removed
        iw dev "${VWIFI_IFACE}" del
        dealloc_vface_name "$VWIFI_IFACE"
    fi
    
    restore_interface_status
    
    if ! has_running_instance; then
        echo "Exiting: This is the only running instance"
        # kill common processes
        for x in $COMMON_CONFDIR/*.pid; do
            [[ -f $x ]] && kill -9 $(cat "$x") && rm "$x"
        done
        
        rm -d "$COMMON_CONFDIR/vfaces"
        rm -d "$COMMON_CONFDIR"
        rm -d "$TMPDIR"
    else
        echo "Exiting: This is NOT the only running instance"
    fi
}

clean_iptables() {
    [[ -f $CONFDIR/undo_iptables.sh ]] && bash $CONFDIR/undo_iptables.sh
    
    [[ -f $CONFDIR/undo_iptables_2.sh ]] && bash $CONFDIR/undo_iptables_2.sh
}

cleanup() {
    trap "" SIGINT SIGUSR1 SIGUSR2 EXIT SIGTERM
    echo
    echo
    echo "Doing cleanup.. "
    kill_processes
    echo "Undoing iptables changes .."
    clean_iptables > /dev/null
    _cleanup 2> /dev/null
    
    #pgid=$(ps opgid= $$ |awk '{print $1}' )
    #echo "Killing PGID $pgid ..."
    #kill -15 -$pgid
    #sleep 1 
    echo "Cleaning up done"
    #kill -9 -$pgid
}

# NOTE function die() is designed NOT to be used before init_trap() executed
die() { # SIGUSR2
    echo "Error occured"
    [[ -n "$1" ]] && echo -e "\nERROR: $1\n" >&2
    # send die signal to the main process
    [[ $BASHPID -ne $$ ]] && kill -USR2 $$ || cleanup
    exit 1
}

clean_exit() { # SIGUSR1
    # send clean_exit signal to the main process
    [[ $BASHPID -ne $$ ]] && kill -USR1 $$ || cleanup
    exit 0
}

init_trap(){
    trap "cleanup" EXIT
    trap "clean_exit" SIGINT SIGUSR1 SIGTERM
    trap "die" SIGUSR2
}
init_conf_dirs() {
    mkdir -p "$TMPDIR" || die "Couldn't make linux-router's temporary dir"
    chmod 755 "$TMPDIR" 2>/dev/null
    cd "$TMPDIR" || die "Couldn't change directory to linux-router's temporary path"

    CONFDIR="$(mktemp -d $TMPDIR/lnxrouter.${TARGET_IFACE}.conf.XXXXXX)" || die "Instance couldn't make config dir" # config dir for one instance
    chmod 755 "$CONFDIR"
    #echo "Config dir: $CONFDIR"
    echo $$ > "$CONFDIR/pid"

    COMMON_CONFDIR="$TMPDIR/lnxrouter_common.conf" # config dir for all instances
    mkdir -p "$COMMON_CONFDIR"
}

#== functions to deal with running instances

list_running_conf() {
    local x
    for x in $TMPDIR/lnxrouter.*; do
        if [[ -f $x/pid && -f $x/subn_iface && -d /proc/$(cat $x/pid) ]]; then
            echo "$x"
        fi
    done
}

list_running() {
    local IFACE subn_iface x
    for x in $(list_running_conf); do
        IFACE=${x#*.}
        IFACE=${IFACE%%.*}
        subn_iface=$(cat $x/subn_iface)

        if [[ "$IFACE" == "$subn_iface" ]]; then
            echo $(cat $x/pid) $IFACE
        else
            echo $(cat $x/pid) $IFACE '('$(cat $x/subn_iface)')'
        fi
    done
}

get_subn_iface_from_pid() {
    list_running | awk '{print $1 " " $NF}' | tr -d '\(\)' | grep -E "^${1} " | cut -d' ' -f2
}

get_pid_from_subn_iface() {
    list_running | awk '{print $1 " " $NF}' | tr -d '\(\)' | grep -E " ${1}$" | cut -d' ' -f1
}

get_confdir_from_pid() {
    local IFACE x
    for x in $(list_running_conf); do
        if [[ $(cat $x/pid) == "$1" ]]; then
            echo "$x"
            break
        fi
    done
}

#======================================================

print_clients_from_leases() {  # MAC|IP|HOST|lease
    local LEASE_FILE="$1"
    local FILEC
    local line
    local LEASEstr LEASEstamp
    
    FILEC="$(cat "$LEASE_FILE" | grep -v -E "^duid\b" | sed -r '/^\s*$/d' )"

    # TODO: duid is somewhat related to ipv6. I don't know about it. Not sure excluding it miss some info or not
    echo "$FILEC" | while read -r line
    do
        #echo aa$line
        LEASEstamp="$(echo "$line" | awk '{print $1}')"
        MAC="$(echo "$line" | awk '{print $2}')"
        IP="$(echo "$line" | awk '{print $3}'  | sed 's/\[//g' | sed 's/\]//g')"
        HOST="$(echo "$line" | awk '{print $4}' | sed 's/*/?/g' | sed 's/|/_/g' | sed 's/ /_/g' )"
        
        if [[ -n "$MAC" ]]; then
            LEASEstr="$(date -d @${LEASEstamp} +%m-%d_%X)"
            
            echo "$MAC|$IP|$HOST|lease_$LEASEstr"
        fi
    done
    
}
print_interface_neighbors_via_iproute() {  # MAC|IP|_|STATUS 
    local IFACE=$1
    
    local line
    
    ip n | grep -E "\bdev $IFACE\b" | sed 's/ /|/g' | while read -r line
    do
        local MAC IP STATUS
        
        IP="$(echo "$line" | awk -F'|' '{print $1}')"
        
        if [[ "$(echo "$line" | awk -F'|' '{print $4}')" == "lladdr" ]]; then # has mac
            # if has mac, $4="lladdr" and $5=macaddress and $6+=status
            MAC="$(echo "$line" | awk -F'|' '{print $5}')"
            STATUS="$(echo "$line" | awk -F'|' '$1="";$2="";$3="";$4="";$5="";{print}' | awk '{$1=$1;print}'| sed 's/ /,/g')"
        else # no mac 
            # if no mac, $4="" and $5+=status
            MAC="?"
            STATUS="$(echo "$line" | awk -F'|' '$1="";$2="";$3="";$4="";{print}' | awk '{$1=$1;print}' | sed 's/ /,/g')"
        fi
        if [[ -n "$IP" && ( "$MAC" != "?" || "$STATUS" != "FAILED" ) ]]; then
            echo "$MAC|$IP|?|$STATUS"
        fi
    done
}
print_interface_neighbors_via_iw() {  # MAC|_|_|signal  
    local IFACE=$1
    local MAC SIGNAL
    iw dev "$IFACE" station dump | awk '($1 ~ /Station$/) {print $2}' | while read -r MAC
    do
        if [[ -n "$MAC" ]]; then
            SIGNAL="$(iw dev "$IFACE" station get "$MAC" | grep "signal:" | awk '{print $2}')"
            echo "${MAC}|?|?|${SIGNAL}_dBm"
        fi
    done
}

list_clients() { # passive mode. (use 'arp-scan' or 'netdiscover' if want active mode)
    local IFACE pid
    local CONFDIR
    
    local output=""
    # If number (PID) is given, get the associated wifi iface
    if [[ "$1" =~ ^[1-9][0-9]*$ ]]; then
        pid="$1"
        IFACE=$(get_subn_iface_from_pid "$pid")
        if [[ -z "$IFACE" ]] ; then
            echo "'$pid' is not the pid of a running $PROGNAME instance." >&2 
            exit 1
        fi
    else # non-number given
        IFACE="$1"
        if ( ! is_interface "$IFACE" ) ; then
            echo "'$IFACE' is not an interface or PID" >&2
            exit 1
        fi
        pid=$(get_pid_from_subn_iface "$IFACE")
        if [[ -n "$pid" ]] ; then  # if this interface is hosted by us
            CONFDIR=$(get_confdir_from_pid "$pid")
            output="$(print_clients_from_leases "$CONFDIR/dnsmasq.leases" )"
        else    # this interface NOT hosted by us
            echo "Tip: '$IFACE' is not an interface hosted by $PROGNAME" >&2
        fi
    fi
    output="$(echo "$output" ; print_interface_neighbors_via_iw "$IFACE") "
    output="$(echo "$output" ; print_interface_neighbors_via_iproute "$IFACE")"
    
    output="$(echo "$output" | sort -k 1 -k 2 -t '|' | uniq | sed -r '/^\s*$/d')"

    echo "$IFACE ($(get_interface_mac "$IFACE")) neighbors:"
    
    local fmt="%-19s%-41s%-20s%s" # string length: MAC 17, ipv4 15, ipv6 39, hostname ?
    printf "$fmt\n"  "MAC" "IP" "HOSTNAME" "INFO"
    
    local line
    echo "$output"| while read -r line
    do
        if [[ -n "$line" ]]; then
            echo "$line" | awk -F'|' "{printf \"$fmt\n\",\$1,\$2,\$3,\$4}"
        fi
    done
    # TODO : merge same mac and same ip line
}

has_running_instance() {
    local PID x

    for x in $TMPDIR/lnxrouter.*; do
        if [[ -f $x/pid ]]; then
            PID=$(cat $x/pid)
            if [[ -d /proc/$PID ]]; then
                return 0
            fi
        fi
    done

    return 1
}

is_running_pid() {
    list_running | grep -E "^${1} " > /dev/null 2>&1
}

send_stop() {
    local x

    # send stop signal to specific pid
    if is_running_pid "$1"; then
        kill -USR1 "$1"
        return
    fi

    # send stop signal to specific interface
    for x in $(list_running | grep -E " \(?${1}( |\)?\$)" | cut -f1 -d' '); do
        kill -USR1 "$x"
    done
}


## ========================================================
## ========================================================
# decide linux-router's global temporary path for all instances
# this is different and should be before config-saving dir. The latter is for one instance
decide_tmpdir(){
    local TMPD
    if [[ -d /dev/shm ]]; then
        TMPD=/dev/shm
    elif [[ -d /run/shm ]]; then
        TMPD=/run/shm
    else
        TMPD=/tmp
    fi
    #TMPDIR=$TMPD/lnxrouter_tmp
    echo "$TMPD/lnxrouter_tmp"
}

#======

check_other_functions(){
    if [[ $LIST_RUNNING -eq 1 ]]; then
        echo -e "List of running $PROGNAME instances:\n"
        list_running
        exit 0
    fi

    if [[ -n "$LIST_CLIENTS_ID" ]]; then
        list_clients "$LIST_CLIENTS_ID"
        exit 0
    fi

    ##### root test ##### NOTE above don't require root ##########
    if [[ $(id -u) -ne 0 ]]; then
        echo "ERROR: Need root to continue" >&2
        exit 1
    fi
    ###### NOTE below require root ##########

    if [[ -n "$STOP_ID" ]]; then
        echo "Trying to kill $PROGNAME instance associated with $STOP_ID..."
        send_stop "$STOP_ID"
        exit 0
    fi
}


daemonizing_check(){
    if [[ $DAEMONIZE -eq 1 && $RUNNING_AS_DAEMON -eq 0 ]]; then
        echo "Running as Daemon..."
        # run a detached lnxrouter
        RUNNING_AS_DAEMON=1 setsid "$0" "${ARGS[@]}" &
        exit 0
    fi
}

#============================
check_wifi_settings() {

    if ! ( which iw > /dev/null 2>&1 && iw dev "$WIFI_IFACE" info > /dev/null 2>&1 ); then
        echo "WARN: Can't use 'iw' to operate interfce '$WIFI_IFACE', trying 'iwconfig' (not as good as 'iw') ... (Did you spell the interface name right?)" >&2
        USE_IWCONFIG=1
    fi
    
    if [[ $USE_IWCONFIG -eq 1 ]]; then
        if ! (which iwconfig > /dev/null 2>&1 && iwconfig "$WIFI_IFACE" > /dev/null 2>&1); then
            echo "ERROR: Can't use 'iwconfig' to operate interfce '$WIFI_IFACE'" >&2
            exit 1
        fi
    fi
    
    if [[ $FREQ_BAND != 2.4 && $FREQ_BAND != 5 ]]; then
        echo "ERROR: Invalid frequency band" >&2
        exit 1
    fi

    if [[ $FREQ_BAND != 5 && $CHANNEL -gt 14 ]]; then
        echo "Channel number is greater than 14, assuming 5GHz frequency band"
        FREQ_BAND=5
    fi

    if ! can_be_ap "${WIFI_IFACE}"; then
        echo "ERROR: Your adapter does not support AP (master) mode" >&2
        exit 1
    fi

    if ! can_be_sta_and_ap "${WIFI_IFACE}"; then
        if is_interface_wifi_connected "${WIFI_IFACE}"; then
            echo "ERROR: Your adapter can not be a station (i.e. be connected) and an AP at the same time" >&2
            exit 1
        elif [[ $NO_VIRT -eq 0 ]]; then
            echo "WARN: Your adapter does not fully support AP virtual interface, enabling --no-virt" >&2
            NO_VIRT=1
        fi
    fi

    HOSTAPD=$(which hostapd)

    if [[ $(get_adapter_kernel_module "${WIFI_IFACE}") =~ ^(8192[cd][ue]|8723a[sue])$ ]]; then
        if ! strings "$HOSTAPD" | grep -m1 rtl871xdrv > /dev/null 2>&1; then
            echo "ERROR: You need to patch your hostapd with rtl871xdrv patches." >&2
            exit 1
        fi

        if [[ $DRIVER != "rtl871xdrv" ]]; then
            echo "WARN: Your adapter needs rtl871xdrv, enabling --driver=rtl871xdrv" >&2
            DRIVER=rtl871xdrv
        fi
    fi
    
    if [[ ${#SSID} -lt 1 || ${#SSID} -gt 32 ]]; then
        echo "ERROR: Invalid SSID length ${#SSID} (expected 1..32)" >&2
        exit 1
    fi

    if [[ $USE_PSK -eq 0 ]]; then
        if [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -lt 8 ]] || [[ ${#PASSPHRASE} -gt 63 ]]; then
            echo "ERROR: Invalid passphrase length ${#PASSPHRASE} (expected 8..63)" >&2
            exit 1
        fi
    elif [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -ne 64 ]]; then
        echo "ERROR: Invalid pre-shared-key length ${#PASSPHRASE} (expected 64)" >&2
        exit 1
    fi

    if [[ $(get_adapter_kernel_module "${WIFI_IFACE}") =~ ^rtl[0-9].*$ ]]; then
        if [[ $WPA_VERSION == '1' || $WPA_VERSION == '1+2' ]]; then
            echo "WARN: Realtek drivers usually have problems with WPA1, WPA2 is recommended" >&2
        fi
        echo "WARN: If AP doesn't work, read https://github.com/oblique/create_ap/blob/master/howto/realtek.md" >&2
    fi

    if [[ -z $VIRT_NAME ]]; then
        if [[ ${#WIFI_IFACE} -gt 13 ]]; then
            echo "WARN: $WIFI_IFACE has ${#WIFI_IFACE} characters which might be too long. If AP doesn't work, see --virt-name and https://github.com/garywill/linux-router/issues/44" >&2
        fi
    elif [[ ${#VIRT_NAME} -gt 15 ]]; then
        echo "WARN: option --virt-name $VIRT_NAME has ${#VIRT_NAME} characters which might be too long, consider making it shorter in case of errors" >&2
    fi

    if [[ ! -z $VIRT_NAME ]] && is_vface_name_allocated "$VIRT_NAME"; then
      echo "WARN: interface $VIRT_NAME aleady exists, this will cause an error"
    fi
}

check_if_new_mac_valid() {
    if ! is_unicast_macaddr "$NEW_MACADDR"; then
        echo "ERROR: The first byte of MAC address (${NEW_MACADDR}) must be even" >&2
        exit 1
    fi

    if [[ $(get_all_mac_in_system | grep -c "${NEW_MACADDR}") -ne 0 ]]; then
        echo "WARN: MAC address '${NEW_MACADDR}' already exists" >&2
    fi
}

decide_target_interface() {
    # TARGET_IFACE is a existing physical interface
    if [[ "$CONN_IFACE" ]]; then
        echo "$CONN_IFACE"
    elif [[ "$WIFI_IFACE" ]]; then
        echo "$WIFI_IFACE"
    else
        echo "No target interface specified"  >&2
        return 1
    fi
}

decide_ip_addresses() {
    if [[ ! -n $GATEWAY4 ]]; then
        GATEWAY4="$(generate_random_ip4)"
        echo "Use random LAN IPv4 address $GATEWAY4"
    elif [[ ! "$GATEWAY4" =~ "." ]]; then
        GATEWAY4="192.168.${GATEWAY4}.1"
    fi

    if [[ $IPV6 -eq 1 && ! -n $PREFIX6 ]]; then
        PREFIX6="$(generate_random_lan_ip6_prefix)"
        echo "Use random LAN IPv6 address ${PREFIX6}${IID6}"
    elif [[ ! "$PREFIX6" =~ ":" ]]; then
        PREFIX6="fd00:0:0:${PREFIX6}::"
    fi
    if [[ $IPV6 -eq 1 ]]; then
        GATEWAY6="${PREFIX6}${IID6}"
    fi
    
    SUBNET_NET4="${GATEWAY4%.*}.0/24"
    [[ $IPV6 -eq 1 ]] && SUBNET_NET6="${PREFIX6}/64"
    
}

prepare_wifi_interface() {
    if [[ $USE_IWCONFIG -eq 0 ]]; then
        iw dev "${WIFI_IFACE}" set power_save off
    fi
    
    if [[ $NO_VIRT -eq 0 ]]; then
    ## Will generate virtual wifi interface
    
        # TODO move this to check_wifi_settings() ?
        if is_interface_wifi_connected "${WIFI_IFACE}"; then
            WIFI_IFACE_FREQ=$(iw dev "${WIFI_IFACE}" link | grep -i freq | awk '{print $2}' | sed 's/\.00*$//g')  # NOTE we assume integer currently, which can be right, or wrong  in the future
            WIFI_IFACE_CHANNEL=$(ieee80211_frequency_to_channel "${WIFI_IFACE_FREQ}")
            
            echo "${WIFI_IFACE} already working in channel ${WIFI_IFACE_CHANNEL} (${WIFI_IFACE_FREQ} MHz)"
            
            if [[ $CHANNEL == default ]]; then
                echo "Use wifi adapter current channel $WIFI_IFACE_CHANNEL as target channel"
                CHANNEL=$WIFI_IFACE_CHANNEL
            fi
            
            if [[ $WIFI_IFACE_CHANNEL -ne $CHANNEL ]]; then
                echo "WARN: Wifi adapter already working in channel ${WIFI_IFACE_CHANNEL}, which is different than target channel $CHANNEL" >&2
            fi
        fi

        echo "Creating a virtual WiFi interface... "
        VWIFI_IFACE=$(alloc_new_vface_name)
        if iw dev "${WIFI_IFACE}" interface add "${VWIFI_IFACE}" type __ap; then
            # Successfully created virtual wifi interface
            # if NM running, it will give the new virtual interface a random MAC. MAC will go back after setting NM unmanaged
            sleep 2  
            echo "${VWIFI_IFACE} created"
        else
            VWIFI_IFACE=
            if [[ ! -z ${VIRT_NAME} ]] && [[ ${#VIRT_NAME} -gt 15 ]]; then
              die "Failed creating virtual WiFi interface. This is likely because you have set a long name for your virtual interface using --virt-name, try making it shorter'"
            elif [[ -z ${VIRT_NAME} ]] && [[ ${#WIFI_IFACE} -gt 13 ]]; then
              die "Failed creating virtual WiFi interface. This is likely because your interface name is too long. Try using '--virt-name <shorter interface name>'"
            else
              die "Failed creating virtual WiFi interface. Maybe your WiFi adapter does not fully support virtual interfaces. Try again with '--no-virt'"
            fi
        fi
        
        AP_IFACE=${VWIFI_IFACE}
    else # no virtual wifi interface, use wifi device interface itself
        AP_IFACE=${WIFI_IFACE}
    fi
    
    if [[ $CHANNEL == default ]]; then
        echo "Channel not specified, use default"
        if [[ $FREQ_BAND == 2.4 ]]; then
            CHANNEL=1
        else
            CHANNEL=36
        fi
    fi
}

decide_subnet_interface() {
    if [[ $WIFI_IFACE ]]; then
        echo "${AP_IFACE}"
    else
        echo "${TARGET_IFACE}"
    fi
}

dealwith_mac() {
    local VMAC
    
    if [[ -n "$NEW_MACADDR" ]] ; then  # user choose to set subnet mac 

        echo "Setting ${SUBNET_IFACE} new MAC address ${NEW_MACADDR} ..."
        set_interface_mac "${SUBNET_IFACE}" "${NEW_MACADDR}" || die "Failed setting new MAC address"
        
    elif [[ $VWIFI_IFACE ]]; then # user didn't choose to set mac, but using virtual wifi interface

        VMAC=$(get_new_macaddr_according_to_existing "${WIFI_IFACE}")
        if [[ "$VMAC" ]]; then
            echo "Assigning MAC address $VMAC to virtual interface $VWIFI_IFACE according to $WIFI_IFACE ..."
            set_interface_mac "$VWIFI_IFACE" "$VMAC"
        fi
    fi
}

write_hostapd_conf() {  
    cat <<- EOF > "$CONFDIR/hostapd.conf"
		beacon_int=100
		ssid=${SSID}
		interface=${AP_IFACE}
		driver=${DRIVER}
		channel=${CHANNEL}
		ctrl_interface=$CONFDIR/hostapd_ctrl
		ctrl_interface_group=0
		ignore_broadcast_ssid=$HIDDEN
		ap_isolate=$ISOLATE_CLIENTS
	EOF

    if [[ -n "$COUNTRY" ]]; then
        cat <<- EOF >> "$CONFDIR/hostapd.conf"
			country_code=${COUNTRY}
			ieee80211d=1
		EOF
    fi

    if [[ $FREQ_BAND == 2.4 ]]; then
        echo "hw_mode=g" >> "$CONFDIR/hostapd.conf"
    else
        echo "hw_mode=a" >> "$CONFDIR/hostapd.conf"
    fi

    if [[ $MAC_FILTER -eq 1 ]]; then
        cat <<- EOF >> "$CONFDIR/hostapd.conf"
			macaddr_acl=${MAC_FILTER}
			accept_mac_file=${MAC_FILTER_ACCEPT}
		EOF
    fi

    if [[ $HOTSPOT20 -eq 1 ]]; then
        echo "hs20=1" >> "$CONFDIR/hostapd.conf"
    fi

    if [[ $IEEE80211N -eq 1 ]]; then
        cat <<- EOF >> "$CONFDIR/hostapd.conf"
			ieee80211n=1
			ht_capab=${HT_CAPAB}
		EOF
    fi

    if [[ $REQUIREHT -eq 1 ]]; then
        echo "require_ht=1" >> "$CONFDIR/hostapd.conf"
    fi

    if [[ $IEEE80211AC -eq 1 ]]; then
        echo "ieee80211ac=1" >> "$CONFDIR/hostapd.conf"
    fi

    if [[ $REQUIREVHT -eq 1 ]]; then
        echo "require_vht=1" >> "$CONFDIR/hostapd.conf"
    fi

    if [[ -n "$VHT_CAPAB" ]]; then
        echo "vht_capab=${VHT_CAPAB}" >> "$CONFDIR/hostapd.conf"
    fi

	if [[ $VHTCHANNELWIDTH -gt 0 ]]; then
		cat <<- EOF >> "$CONFDIR/hostapd.conf"
			vht_oper_chwidth=${VHTCHANNELWIDTH}
		EOF
	fi

	if [[ $VHTSEG0CHINDEX -gt 0 ]]; then
		cat <<- EOF >> "$CONFDIR/hostapd.conf"
			vht_oper_centr_freq_seg0_idx=${VHTSEG0CHINDEX}
		EOF
	fi

	if [[ $VHTSEG1CHINDEX -gt 0 ]]; then
		cat <<- EOF >> "$CONFDIR/hostapd.conf"
			vht_oper_centr_freq_seg1_idx=${VHTSEG1CHINDEX}
		EOF
	fi

    if [[ $IEEE80211N -eq 1 ]] || [[ $IEEE80211AC -eq 1 ]]; then
        echo "wmm_enabled=1" >> "$CONFDIR/hostapd.conf"
    fi

    if [[ -n "$PASSPHRASE" ]]; then
        [[ "$WPA_VERSION" == "1+2" ]] && WPA_VERSION=3
        if [[ $USE_PSK -eq 0 ]]; then
            WPA_KEY_TYPE=passphrase
        else
            WPA_KEY_TYPE=psk
        fi
        cat <<- EOF >> "$CONFDIR/hostapd.conf"
			wpa=${WPA_VERSION}
			wpa_${WPA_KEY_TYPE}=${PASSPHRASE}
			wpa_key_mgmt=WPA-PSK
			wpa_pairwise=CCMP
			rsn_pairwise=CCMP
		EOF
    else
        echo "WARN: WiFi is not protected by password" >&2
    fi
    chmod 600 "$CONFDIR/hostapd.conf"
}

write_dnsmasq_conf() {
    local i
    if grep "^nobody:" /etc/group >/dev/null 2>&1 ; then
        NOBODY_GROUP="nobody"
    else
        NOBODY_GROUP="nogroup"
    fi
    
    mkfifo "$CONFDIR/dnsmasq.log" || die "Failed creating pipe file for dnsmasq"
    chown nobody "$CONFDIR/dnsmasq.log" || die "Failed changing dnsmasq log file owner"
    cat "$CONFDIR/dnsmasq.log" & 
    
    cat <<- EOF > "$CONFDIR/dnsmasq.conf"
		user=nobody
		group=$NOBODY_GROUP
		bind-dynamic
		listen-address=${GATEWAY4}
		interface=$SUBNET_IFACE
		except-interface=lo
		no-dhcp-interface=lo
		dhcp-range=${GATEWAY4%.*}.10,${GATEWAY4%.*}.250,255.255.255.0
		dhcp-option-force=option:router,${GATEWAY4}
		#log-dhcp
		log-facility=$CONFDIR/dnsmasq.log
		bogus-priv
		domain-needed
	EOF
    # 'log-dhcp'(Extra logging for DHCP) shows too much logs.
    # if use '-d', 'log-facility' should = /dev/null
    if [[ $SHARE_METHOD == "none" ]]; then    
        echo "no-resolv"  >> "$CONFDIR/dnsmasq.conf"
        echo "no-poll" >> "$CONFDIR/dnsmasq.conf"
    fi
    if [[ "$DHCP_DNS" != "no" ]]; then
        if [[ "$DHCP_DNS" == "gateway" ]]; then
            dns_offer="$GATEWAY4"
        else
            dns_offer="$DHCP_DNS"
        fi
        echo "dhcp-option-force=option:dns-server,${dns_offer}" >> "$CONFDIR/dnsmasq.conf"
    fi
    
    if [[ ! "$dnsmasq_NO_DNS" -eq 0 ]]; then
        echo "port=0"  >> "$CONFDIR/dnsmasq.conf"
    fi

    [[ -n "$MTU" ]] && echo "dhcp-option-force=option:mtu,${MTU}" >> "$CONFDIR/dnsmasq.conf"
    [[ $ETC_HOSTS -eq 0 ]] && echo no-hosts >> "$CONFDIR/dnsmasq.conf"
    [[ -n "$ADDN_HOSTS" ]] && echo "addn-hosts=${ADDN_HOSTS}" >> "$CONFDIR/dnsmasq.conf"
    if [[ "$THISHOSTNAME" ]]; then
        [[ "$THISHOSTNAME" == "-" ]] && THISHOSTNAME="$(cat /etc/hostname)"
        echo "interface-name=$THISHOSTNAME,$SUBNET_IFACE" >> "$CONFDIR/dnsmasq.conf"
    fi
    if [[ ! "$SHOW_DNS_QUERY" -eq 0 ]]; then
        echo log-queries=extra >> "$CONFDIR/dnsmasq.conf"
    fi
    
    if [[ $DNS ]]; then
        DNS_count=$(echo "$DNS" | awk -F, '{print NF}')
        for (( i=1;i<=DNS_count;i++ )); do
            sep_ip_port "$(echo "$DNS" | cut -d, -f$i)" DNS_IP DNS_PORT
            [[ "$DNS_PORT" ]] && DNS_PORT_D="#$DNS_PORT"
            echo "server=${DNS_IP}${DNS_PORT_D}" >> "$CONFDIR/dnsmasq.conf"
        done
        
        cat <<- EOF >> "$CONFDIR/dnsmasq.conf"
			no-resolv
			no-poll
		EOF
    fi
    if [[ $DNS_NOCACHE -eq 1 ]]; then
        echo "cache-size=0" >> "$CONFDIR/dnsmasq.conf"
        echo "no-negcache" >> "$CONFDIR/dnsmasq.conf"
    fi
    if [[ $IPV6 -eq 1 ]];then
        cat <<- EOF  >> "$CONFDIR/dnsmasq.conf"
			listen-address=${GATEWAY6}
			enable-ra
			#quiet-ra
			dhcp-range=interface:${SUBNET_IFACE},::,::ffff:ffff:ffff:ffff,constructor:${SUBNET_IFACE},ra-stateless,64
		EOF
        if [[ "$DHCP_DNS6" != "no" ]]; then
            if [[ "$DHCP_DNS6" == "gateway" ]]; then
                dns_offer6="[$GATEWAY6]"
            else
                dns_offer6="$DHCP_DNS6"
            fi
            echo "dhcp-option=option6:dns-server,${dns_offer6}" >> "$CONFDIR/dnsmasq.conf"
        fi
    fi
}

run_wifi_ap_processes() {
    if [[ $NO_HAVEGED -eq 0 ]]; then
        haveged_watchdog &
        HAVEGED_WATCHDOG_PID=$!
        echo "$HAVEGED_WATCHDOG_PID" > "$CONFDIR/haveged_watchdog.pid"
        echo
        echo "haveged_watchdog PID: $HAVEGED_WATCHDOG_PID" 
    fi

    # start access point
    #echo "hostapd command-line interface: hostapd_cli -p $CONFDIR/hostapd_ctrl"
    # start hostapd (use stdbuf when available for no delayed output in programs that redirect stdout)
    STDBUF_PATH=$(which stdbuf)
    if [ $? -eq 0 ]; then
        STDBUF_PATH=$STDBUF_PATH" -oL"
    fi
    echo 
    echo "Starting hostapd"
    
    if COMPLAIN_CMD="$(command -v aa-complain || command -v complain)"; then
        echo "Setting hostapd to AppArmor complain mode..."
        "$COMPLAIN_CMD" hostapd
    fi
    
    # hostapd '-P' works only when use '-B' (run in background)
    $STDBUF_PATH hostapd $HOSTAPD_DEBUG_ARGS -P "$CONFDIR/hostapd.pid" "$CONFDIR/hostapd.conf"  &
    HOSTAPD_PID=$!
    echo "$HOSTAPD_PID" > "$CONFDIR/hostapd.pid"
    echo "hostapd PID: $HOSTAPD_PID"
    #while [[ ! -f $CONFDIR/hostapd.pid ]]; do
    #    sleep 1
    #done
    #echo -n "hostapd PID: " ; cat $CONFDIR/hostapd.pid
    pid_watchdog "$HOSTAPD_PID" 10 "hostapd failed. (tip: try '--hostapd-debug' to get some debug info)" &
    sleep 3
}

start_dnsmasq() {
    echo 
    echo "Starting dnsmasq"
    
    if COMPLAIN_CMD="$(command -v aa-complain || command -v complain)"; then
        echo "Setting dnsmasq to AppArmor complain mode..."
        "$COMPLAIN_CMD" dnsmasq
    fi
    
    # Using '-d'(no daemon) dnsmasq will not turn into 'nobody'
    # '-x' works only when no '-d'
    dnsmasq  -k -C "$CONFDIR/dnsmasq.conf" -x "$CONFDIR/dnsmasq.pid" -l "$CONFDIR/dnsmasq.leases" & 
    #####DNSMASQ_PID=$!         # only when with '-d'
    ######echo "dnsmasq PID: $DNSMASQ_PID"      # only when with '-d'
    i=0; while [[ ! -f "$CONFDIR/dnsmasq.pid" ]]; do
        sleep 1
        i=$((i + 1))
        if [[ $i -gt 10 ]]; then die "Couldn't get dnsmasq PID" ; fi
    done
    DNSMASQ_PID="$(cat "$CONFDIR/dnsmasq.pid" )"
    echo  "dnsmasq PID: $DNSMASQ_PID" 
    ######(wait $DNSMASQ_PID ; die "dnsmasq failed") &  # wait can't deal with non-child
    pid_watchdog "$DNSMASQ_PID" 9 "dnsmasq failed" &
    sleep 2
}

check_rfkill_unblock_wifi() {
    local PHY
    if which rfkill > /dev/null 2>&1 ; then
        PHY=$(get_interface_phy_device "${SUBNET_IFACE}")
        [[ -n $PHY ]] && rfkill unblock $(rfkill | grep "$PHY" | awk '{print $1}') >/dev/null 2>&1
    fi
}

#=========== Above are functions ======================
#=========== Executing begin ==============================

# if empty option, show usage and exit 
check_empty_option "$@"

# TODO: are some global variables are still defined in those following code?
define_global_variables

ARGS=( "$@" )

parse_user_options "$@"
# TODO: detect user option conflict


TMPDIR="$(decide_tmpdir)"

# if user choose to deal with running instances, will output some info then exit after this 
# NOTE above don't require root
check_other_functions 
# NOTE below require root

# if user choose to daemonize, will start new background process and exit this 
daemonizing_check

# check if wifi will work on this system and user settings
[[ $WIFI_IFACE ]] && check_wifi_settings

[[ -n "$NEW_MACADDR" ]] && check_if_new_mac_valid # check NEW_MACADDR. will exit if not valid

# checks finished

## ===== Above don't echo anything if no warning or error====================
## ========================================================
phead
phead2
echo

echo "PID: $$"

TARGET_IFACE="$(decide_target_interface)" || exit 1 # judge wired (-i CONN_IFACE) or wireless hotspot (--ap $WIFI_IFACE)
echo "Target interface is ${TARGET_IFACE} ($(get_interface_mac "$TARGET_IFACE")) "
show_interface_pci_info "$TARGET_IFACE"

if [[ "$MAC_USE_RANDOM" -eq 1 ]] ; then
    NEW_MACADDR="$(generate_random_mac)"
    echo "Use random MAC address $NEW_MACADDR"
fi

decide_ip_addresses # ip 4 & 6 lan addresses

# if user choose to make DHCP to tell clients to use other DNS, we don't have to serve DNS
[[ $DHCP_DNS != 'gateway' && $DHCP_DNS6 != 'gateway' ]] && dnsmasq_NO_DNS=1

#===========================================================
#==== begin to do some change on config files and system===

init_trap
# NOTE function die() is designed not to be used before init_trap() executed

init_conf_dirs #   CONFDIR  , COMMON_CONFDIR  . make dir

[[ $WIFI_IFACE ]] && prepare_wifi_interface # this will create virtual ap interface (if needed) and set VWIFI_IFACE and AP_IFACE (if success)

SUBNET_IFACE="$(decide_subnet_interface)"  # SUBNET_IFACE can be TARGET_IFACE (wired) or AP_IFACE (ap) .this is after prepare_wifi_interface()
echo "$SUBNET_IFACE" > "$CONFDIR/subn_iface"

# if virtual wifi interface, will be destroyed, so only need to save status when not
[[ -z $VWIFI_IFACE ]] && backup_interface_status

# TODO: should these 2 before calling prepare_wifi_interface ? in check_wifi_settings() ?
# set iw country code
if [[ $WIFI_IFACE && -n "$COUNTRY" && $USE_IWCONFIG -eq 0 ]]; then
    iw reg set "$COUNTRY" || die "Failed setting country code"
fi

# judge channel availability after changing country code
if [[ $WIFI_IFACE ]] ; then
    can_transmit_to_channel "${AP_IFACE}" ${CHANNEL} || die "Your adapter can not transmit to channel ${CHANNEL}, frequency band ${FREQ_BAND}GHz."
fi

[[ $WIFI_IFACE ]] && write_hostapd_conf
#===================================================
#===================================================

# set interface unmanaged by networkManager
if is_nm_running && nm_knows "$TARGET_IFACE"; then # if nm knows target iface, should know subnet iface too. but need to wait until nm finds subnet iface (waiting code is in nm_set_unmanaged()
    nm_set_unmanaged "${SUBNET_IFACE}" # will write NM_UNM_LIST
fi

[[ $NO_DNSMASQ -eq 0 ]] && write_dnsmasq_conf
#===========================

# initialize subnet interface
# take subnet interface down first
ip link set down dev "${SUBNET_IFACE}" || die "Failed setting ${SUBNET_IFACE} down"
# flush old IPs of subnet interface
ip addr flush "${SUBNET_IFACE}" || die "Failed flush ${SUBNET_IFACE} IP"

dealwith_mac # setting MAC should be after setting NM unmanaged

[[ $WIFI_IFACE ]] && check_rfkill_unblock_wifi


echo
iptables --version
echo "Notice: Not showing all operations done to iptables rules"

if [[ "$IPV6" -eq 0 ]]; then
    IP_VERs=("4")
else
    IP_VERs=("4" "6")
fi

disable_unwanted_forwarding 


# bring subnet interface up
ip link set up dev "${SUBNET_IFACE}" || die "Failed bringing ${SUBNET_IFACE} up"

# hostapd , haveged
[[ $WIFI_IFACE ]] && run_wifi_ap_processes

# add ipv4 address to subnet interface
ip -4 addr add ${GATEWAY4}/24 broadcast ${GATEWAY4%.*}.255 dev ${SUBNET_IFACE} || die "Failed setting ${SUBNET_IFACE} IPv4 address"

set_ipv6_bits

# add ipv6 address to subnet interface
if [[ $IPV6 -eq 1 ]] ; then
    ip -6 addr add ${GATEWAY6}/64  dev ${SUBNET_IFACE} || die "Failed setting ${SUBNET_IFACE} IPv6 address"
fi


# enable Internet sharing
if [[ "$SHARE_METHOD" == "none" ]]; then

    echo "No Internet sharing"
    
    [[ "$BANLAN" -eq 1 ]] && start_ban_lan
    
elif [[ "$SHARE_METHOD" == "nat" ]]; then
    [[ "$INTERNET_IFACE" && "$dnsmasq_NO_DNS" -eq 0 ]] && echo -e "\nWARN: You specified Internet interface but this host is providing local DNS. In some unexpected case (eg. mistaken configurations), queries may leak to other interfaces, which you should be aware of.\n" >&2
    
    start_nat
    
    [[ "$BANLAN" -eq 1 ]] && start_ban_lan
    
    echo 1 > "/proc/sys/net/ipv4/ip_forward" || die "Failed enabling system ipv4 forwarding" # TODO maybe uneeded in '--no4' mode
    
    if [[ $IPV6 -eq 1 ]]; then
        echo 1 > "/proc/sys/net/ipv6/conf/all/forwarding" || die "Failed enabling system ipv6 forwarding" # TODO if '-o' used, set only 2 interfaces' bits
    fi
    
    # to enable clients to establish PPTP connections we must
    # load nf_nat_pptp module
    modprobe nf_nat_pptp > /dev/null 2>&1 && echo "Loaded kernel module nf_nat_pptp"
    
elif [[ "$SHARE_METHOD" == "redsocks" ]]; then

    if [[ $IPV6 -eq 1 ]]; then
        echo 1 > "/proc/sys/net/ipv6/conf/$SUBNET_IFACE/forwarding" || die "Failed enabling $SUBNET_IFACE ipv6 forwarding" # to set NA router bit
    fi
    
    [[ "$dnsmasq_NO_DNS" -eq 0 && ! $DNS ]] &&  echo -e "\nWARN: You are using in transparent proxy mode but this host is providing local DNS. In some unexpected case (eg. mistaken configurations), queries may leak to other interfaces, which you should be aware of.\n" >&2

    [[ "$BANLAN" -eq 1 ]] && start_ban_lan
    
    start_redsocks
fi

# start dhcp + dns (optional)

# allow dns port input even if we don't run dnsmasq
# user can serve their own dns server
[[ "$DHCP_DNS" == "gateway" || "$DHCP_DNS6" == "gateway" ]] && allow_dns_port

[[ "$CATCH_DNS" -eq 1 ]] && start_catch_dns

[[ $NO_DNSMASQ -eq 0 ]] && ( allow_dhcp ; start_dnsmasq )


echo ""
is_firewalld_running && firewalld_add_tmpzone


echo 
echo "== Setting up completed, now linux-router should be working =="

#============================================================
#============================================================
#============================================================

show_qr() {
    local T S P H
    S="$SSID"
    if [[ -n "$PASSPHRASE" ]]; then
        T="WPA"
        P="$PASSPHRASE"
    else
        T="nopass"
    fi
    [[ "$HIDDEN" -eq 1 ]] && H="true"
    echo "Scan QR code on phone to connect to WiFi"
    qrencode -m 2 -t ANSIUTF8 "WIFI:T:${T};S:${S};P:${P};H:${H};"
    echo "Use this command to save QR code to image file:"
    echo "    qrencode -m 2 -o <file> \"WIFI:T:${T};S:${S};P:${P};H:${H};\""
    echo
}

[[ "$QR" -eq 1 ]] && show_qr

# need loop to keep this script running
bash -c "while :; do sleep 8000 ; done " &
KEEP_RUNNING_PID=$!
echo "$KEEP_RUNNING_PID" > "$CONFDIR/keep_running.pid"
wait "$KEEP_RUNNING_PID"

clean_exit
